#include "bga/graphics/Drawable.h"

#include "bga/graphics/TextureObject.h"

#include <iostream>
#include <cstring>

#include <typeinfo>

using namespace std;

namespace bga
{

Drawable::Drawable() : Resource(), vertexArray(NULL), colorsArray(NULL), texCoordArray(NULL), normalsArray(NULL), indexArray(NULL),
                        vertexArraySize(0), colorsArraySize(0), texCoordArraySize(0), normalsArraySize(0),
                        vertexArraySizeInByte(0), colorsArraySizeInByte(0), texCoordArraySizeInByte(0), normalsArraySizeInByte(0),
                        indexArraySizeInByte(0), primitive(GL_TRIANGLES), vbo(NULL), ebo(NULL)
{
    /// default value
    this->nbTexCoordPerElement = 2;
}

Drawable::~Drawable()
{
    if(this->vertexArray != NULL)
    {
        delete[] this->vertexArray;
        this->vertexArray = NULL;
    }

    if(this->texCoordArray != NULL)
    {
        delete[] this->texCoordArray;
        this->texCoordArray = NULL;
    }

    if(this->colorsArray != NULL)
    {
        delete[] this->colorsArray;
        this->colorsArray = NULL;
    }

    if(this->indexArray != NULL)
    {
        delete[] this->indexArray;
        this->indexArray = NULL;
    }

    if (vbo != NULL)
    {
        delete this->vbo;
        this->vbo = NULL;
    }
    if (this->ebo != NULL)
    {
        delete this->ebo;
        this->ebo = NULL;
    }
}

const float* Drawable::getVertexArray() const
{
    return this->vertexArray;
}

const float* Drawable::getTexCoordArray() const
{
    return this->texCoordArray;
}

const float* Drawable::getColorsArray() const
{
    return this->colorsArray;
}

const float* Drawable::getNormalsArray() const
{
    return this->normalsArray;
}

const float* Drawable::getVertexAttribArray() const
{
    return this->vertexArray;
}

const unsigned short* Drawable::getIndexArray() const
{
    return this->indexArray;
}

void Drawable::setVertexArray(const float* vertexArray, unsigned int vertexArraySize)
{
    this->vertexArraySize = vertexArraySize;
    this->vertexArraySizeInByte = vertexArraySize * sizeof(float);
    this->vertexArray = new float[vertexArraySize];
    memcpy(this->vertexArray, vertexArray, this->vertexArraySizeInByte);
}

void Drawable::setTexCoordArray(const float* texCoordArray, unsigned int texCoordArraySize)
{
    this->texCoordArraySize = texCoordArraySize;
    this->texCoordArraySizeInByte = texCoordArraySize * sizeof(float);
    this->texCoordArray = new float[texCoordArraySize];
    memcpy(this->texCoordArray, texCoordArray, this->texCoordArraySizeInByte);
}

void Drawable::setColorsArray(const float* colorsArray, unsigned int colorsArraySize)
{
    this->colorsArraySize = colorsArraySize;
    this->colorsArraySizeInByte = colorsArraySize * sizeof(float);
    this->colorsArray = new float[colorsArraySize];

    memcpy(this->colorsArray, colorsArray, this->colorsArraySizeInByte);
}

void Drawable::setNormalsArray(const float* normalsArray, unsigned int normalsArraySize)
{
    this->normalsArraySize = normalsArraySize;
    this->normalsArraySizeInByte = normalsArraySize * sizeof(float);
    this->normalsArray = new float[normalsArraySize];

    memcpy(this->normalsArray, normalsArray, this->normalsArraySizeInByte);
}

void Drawable::setVertexAttribArray(const float* vertexAttribArray, unsigned int vertexAttribArraySize)
{
    this->vertexAttribArraySize = vertexAttribArraySize;
    this->vertexAttribArraySizeInByte = vertexAttribArraySize * sizeof(float);
    this->vertexAttribArray = new float[vertexAttribArraySize];

    memcpy(this->vertexAttribArray, vertexAttribArray, this->vertexAttribArraySizeInByte);
}

void Drawable::setIndexArray(unsigned short* indexArray, unsigned int indexArraySize)
{
    this->indexArraySize = indexArraySize;
    this->indexArraySizeInByte = indexArraySize * sizeof(unsigned short);
    this->indexArray = new unsigned short[indexArraySize];

    memcpy(this->indexArray, indexArray, this->indexArraySizeInByte);
}

GLsizei Drawable::getVertexArraySizeInByte() const
{
    return this->vertexArraySizeInByte;
}

GLsizei Drawable::getTexCoordArraySizeInByte() const
{
    return this->texCoordArraySizeInByte;
}

GLsizei Drawable::getColorsArraySizeInByte() const
{
    return this->colorsArraySizeInByte;
}

GLsizei Drawable::getNormalsArraySizeInByte() const
{
    return this->normalsArraySizeInByte;
}

GLsizei Drawable::getVertexAttribArraySizeInByte() const
{
    return this->vertexAttribArraySizeInByte;
}

GLsizei Drawable::getIndexArraySizeInByte() const
{
    return this->indexArraySizeInByte;
}

unsigned int Drawable::getVertexArraySize() const
{
    return this->vertexArraySize;
}

unsigned int Drawable::getTexCoordArraySize() const
{
    return this->texCoordArraySize;
}

unsigned int Drawable::getColorsArraySize() const
{
    return this->colorsArraySize;
}

unsigned int Drawable::getNormalsArraySize() const
{
    return this->normalsArraySize;
}

unsigned int Drawable::getVertexAttribArraySize() const
{
    return this->vertexAttribArraySize;
}

unsigned int Drawable::getIndexArraySize() const
{
    return this->indexArraySize;
}

void Drawable::setNbTexCoordPerElement(unsigned int nbTexCoordPerElement)
{
    this->nbTexCoordPerElement = nbTexCoordPerElement;
}

unsigned int Drawable::getNbTexCoordPerElement() const
{
    return this->nbTexCoordPerElement;
}

GLenum Drawable::getPrimitive() const
{
    return this->primitive;
}

void Drawable::setPrimitive(GLenum primitive)
{
    this->primitive = primitive;
}

VertexBufferObject* Drawable::getVBO() const
{
    return this->vbo;
}

VertexBufferObject* Drawable::getEBO() const
{
    return this->ebo;
}

void Drawable::createAndConfigureVBOs()
{
    if (this->vbo != NULL)
    {
        delete this->vbo;
    }
    /// create VBO according to the drawable
    this->vbo = new VertexBufferObject();
    this->vbo->setTarget(GL_ARRAY_BUFFER_ARB);
    this->vbo->setMode(GL_STATIC_DRAW); //TODO faire un parametre dans drawble pour le mode d'affichage
    this->vbo->bind();

    /// store Drawable's data to VBO (position - normal - color - texCoord)
    this->vbo->allocateBuffer(this->vertexArraySizeInByte + this->normalsArraySizeInByte + this->colorsArraySizeInByte + this->texCoordArraySizeInByte);
    this->vbo->setData(0, this->vertexArraySizeInByte, getVertexArray());
    this->vbo->setData(this->vertexArraySizeInByte, this->normalsArraySizeInByte, getNormalsArray());
    this->vbo->setData(this->vertexArraySizeInByte + this->normalsArraySizeInByte, this->colorsArraySizeInByte, getColorsArray());
    this->vbo->setData(this->vertexArraySizeInByte + this->normalsArraySizeInByte + this->colorsArraySizeInByte, this->texCoordArraySizeInByte, this->getTexCoordArray());
    this->vbo->unbind();

    if (this->ebo != NULL)
    {
        delete this->ebo;
    }
    /// create VBO to store indices
    this->ebo = new VertexBufferObject();
    this->ebo->setTarget(GL_ELEMENT_ARRAY_BUFFER_ARB);
    this->ebo->setMode(GL_STATIC_DRAW);
    this->ebo->bind();

    /// store Drawable's indices to VBO
    this->ebo->allocateBuffer(indexArraySizeInByte);
    this->ebo->setData(0, indexArraySizeInByte, getIndexArray());

    // TODO faire methode static generale pour recupere les erreurs en debug
    GLenum errorId = glGetError();
    if (errorId != GL_NO_ERROR)
    {
        cout << "Error in Drawable::createAndConfigureVBOs" << endl;
    }
    this->ebo->unbind();
}

Vector3f Drawable::getSize()
{
    Vector3f v(0.f, 0.f, 0.f);
    if(this->vertexArraySize > 0)
    {
        float minx = this->vertexArray[0];
        float miny = this->vertexArray[1];
        float minz = this->vertexArray[2];

        float maxx = this->vertexArray[0];
        float maxy = this->vertexArray[1];
        float maxz = this->vertexArray[2];

        for(unsigned int i = 1; i < this->vertexArraySize / 3; i++)
        {
            minx = this->vertexArray[i * 3] < minx ? this->vertexArray[i * 3] : minx;
            miny = this->vertexArray[i * 3 + 1] < miny ? this->vertexArray[i * 3 + 1] : miny;
            minz = this->vertexArray[i * 3 + 2] < minz ? this->vertexArray[i * 3 + 2] : minz;

            maxx = this->vertexArray[i * 3] > maxx ? this->vertexArray[i * 3] : maxx;
            maxy = this->vertexArray[i * 3 + 1] > maxy ? this->vertexArray[i * 3 + 1] : maxy;
            maxz = this->vertexArray[i * 3 + 2] > maxz ? this->vertexArray[i * 3 + 2] : maxz;
        }

        v.setXYZ(maxx - minx, maxy - miny, maxz - minz);
    }

    return v;

}

}
